package Statistics;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

import Definitions.GraphClass;
import Definitions.NodeClass;
import Global.GlobalClass;
import ShortestPath.DijkstraEngine;
import ShortestPath.MapHolder;
import Utility.WorkerUtilityClass;

public class metricsCalculatorInParallelClass {
	

	
	public class EccHolderClass
	{
		Double maximumEcc;
		Integer Xindx;
		Integer Yindx;
		Integer numberOfReachableNodes;
		Integer numberOfUnReachableNodes;
		
		EccHolderClass()
		{
			maximumEcc = 0.0; 
			Xindx = -1;
			Yindx = -1;		
			numberOfReachableNodes = 0;
			numberOfUnReachableNodes = 0;
		}
		
		void print()
		{
			System.out.println("maximumEcc:"+maximumEcc+" Xindx:"+Xindx+" Yindx:"+Yindx);			
			System.out.println("numberOfReachableNodes:"+numberOfReachableNodes+" numberOfUnReachableNodes:"+numberOfUnReachableNodes);
		}
	}
	
	EccHolderClass eccHolder[];	
	GraphClass graph;
	int edgeType;
	
	public metricsCalculatorInParallelClass(GraphClass graph, int edgeType)
	{
		this.graph = graph;
		this.edgeType = edgeType;
		
  	  	/*
  	  	
  	  	Node x=null, y=null;
		
		int sizeOfTheDataSet = graph.getDataSet().size();
		
		ArrayList <Node> dataSet = graph.getDataSet();
		
		double currentDistance, totalDistanceOfAllPaths=0, maxDistance=0;
		int nodeXIndex=-1, nodeYIndex=-1;
		int numberOfPathsBetweenDistanceIsInfinite=0, totalNumberOfPathsFound=0;
		
		System.out.println("Zor ve uzun islem basliyor");
		
		eccHolder = new EccHolderClass[dataSet.size()];
		for(int k=0;k<eccHolder.length; k++)
			eccHolder[k]=new EccHolderClass();
			
  	  
  	  	for(int i=0; i<sizeOfTheDataSet; i++)
  	  	{
  	  		System.out.println("Now processing i:" + i + "/" + sizeOfTheDataSet);
  	  		
  	  		for(int j=i; j<sizeOfTheDataSet; j++)
  	  		{
  	  			x=dataSet.get(i);
  	  			y=dataSet.get(j);
  	  			engine.execute(x, y);
  	  			currentDistance=engine.getShortestDistance(y);
  	  			if(currentDistance!=DijkstraEngine.INFINITE_DISTANCE)
  	  			{
  	  				eccHolder[i].numberOfReachableNodes++;
  	  				eccHolder[j].numberOfReachableNodes++;
  	  				
  	  				totalNumberOfPathsFound++;
  	  				totalDistanceOfAllPaths += currentDistance;
  	  				if(currentDistance > maxDistance)
  	  				{
  	  					System.out.println("maxDistance:"+maxDistance+" currentDistance:"+currentDistance);
  	  					maxDistance = currentDistance;
  	  					nodeXIndex=i;
  	  					nodeYIndex=j;
  	  				}
  	  				
  	  				if(currentDistance>eccHolder[i].maximumEcc)
  	  				{
  	  					eccHolder[i].maximumEcc=currentDistance;
  	  					eccHolder[i].Xindx = i;
  	  					eccHolder[i].Yindx = j;
  	  					
  	  					if(currentDistance>eccHolder[j].maximumEcc)
  	  					{
  	  						eccHolder[j].maximumEcc=currentDistance;
  	  						eccHolder[j].Xindx = j;
  	  						eccHolder[j].Yindx = i;
  	  					}	  				
  	  				}
  	  			}
  	  			else
  	  			{
  	  				numberOfPathsBetweenDistanceIsInfinite++;
  	  				eccHolder[i].numberOfUnReachableNodes++;
  	  				eccHolder[j].numberOfUnReachableNodes++;
  	  			}
  	  				
  	  		}
  	  	}
  	  
  	  	System.out.println("maxDistance:"+maxDistance);
  	  	System.out.println("nodeXIndex:"+nodeXIndex);
  	  	System.out.println("Order of X:"+dataSet.get(nodeXIndex).getOrder());
  	  	System.out.println("ID of X:"+dataSet.get(nodeXIndex).getId());
  	  	System.out.println("nodeYIndex:"+nodeYIndex);
  	  	System.out.println("Order of Y:"+dataSet.get(nodeYIndex).getOrder());
	  	System.out.println("ID of Y:"+dataSet.get(nodeYIndex).getId());  	  	
  	  	System.out.println("totalNumberOfPathsFound:"+totalNumberOfPathsFound);
  	  	System.out.println("numberOfPathsBetweenDistanceIsInfinite:"+numberOfPathsBetweenDistanceIsInfinite);  	  
  	  	System.out.println("totalDistanceOfAllPaths:"+totalDistanceOfAllPaths);
  	  	System.out.println("Ort distance:"+(2*totalDistanceOfAllPaths)/(sizeOfTheDataSet*(sizeOfTheDataSet-1))); 	  	
  	  	System.out.println("Ort distance deneme:"+(double)(totalDistanceOfAllPaths)/(totalNumberOfPathsFound));
  	  	*/
  	  	
  	  	/*
  	  	System.out.println("Now printing ecc:");
  	  	
  	  	for(int k=0; k <eccHolder.length; k++)
  	  	{
  	  		eccHolder[k].print(); 	  		
  	  	}*/
  	  	
  	  	/*
  	  	Double minOfMaxEcc=Double.MAX_VALUE;
  	  	Integer indexOfRadiusNode=-1;
  	  	
  	  	
  	  	
  	  	
  	  	for(int k=0; k <eccHolder.length; k++)
  	  	{
  	  		if(eccHolder[k].maximumEcc!= 0 && eccHolder[k].maximumEcc < minOfMaxEcc)
  	  		{
  	  			minOfMaxEcc = eccHolder[k].maximumEcc;
  	  			indexOfRadiusNode = k;  	  			
  	  		}  	  		
  	  	}
  	  	
  	  System.out.println("minOfMaxEcc:"+minOfMaxEcc);
  	  System.out.println("indexOfRadiusNode:"+indexOfRadiusNode);
	  System.out.println("Order of RadiusNode:"+dataSet.get(indexOfRadiusNode).getOrder());
  	  System.out.println("ID of RadiusNode:"+dataSet.get(indexOfRadiusNode).getId());
  	  
  	  System.out.println("minOfMaxEcc:"+eccHolder[indexOfRadiusNode].maximumEcc+"X:" +eccHolder[indexOfRadiusNode].Xindx+" Y:"+eccHolder[indexOfRadiusNode].Yindx);
  	  */ 	   	  
    }
	
	public void calculateAllShortestPaths()
    {
    	int numberOfCores;
    	int datasetSize=graph.getDataSet().size();
    	
    	double[][] shortestPathsArray = new double[datasetSize][datasetSize];
    	double[] betweennessCentralityArray = new double[datasetSize];
    	
    	WorkerUtilityClass.initializeTwoDimensionalArray(shortestPathsArray, 0);
    	Arrays.fill(betweennessCentralityArray, 0);
    	
		numberOfCores = GlobalClass.RunningValues.maximumNumberOfCPUCoresToBeUsedForNotMuchRamRequiringAndTimeConsumingTasks;
		
		shortestPathDijkstraProcessorPool = new shortestPathDijkstraProcessorThreadClass[numberOfCores];
		for(int i=0; i<numberOfCores; i++)
		{
			shortestPathDijkstraProcessorPool[i] = new shortestPathDijkstraProcessorThreadClass(i, shortestPathsArray, betweennessCentralityArray, numberOfCores);
			shortestPathDijkstraProcessorPool[i].start();		
		}
		
		for(int i=0; i<numberOfCores; i++)
		{				
			try
			{
				shortestPathDijkstraProcessorPool[i].join();
			}
			catch(Exception e)
			{
				e.printStackTrace();
			}	
		}
		
		double numberOfPathsFound=0;
		double cumulativeLengthOfThePaths=0;
		double shortestPathOfTheElements;
		double diameter=0;
		
		
		Map<Double, Integer> shortestPathDistibutionMap = new HashMap<Double, Integer>(); 
		
		for(int i=0; i<datasetSize; i++)
			for(int j=i; j<datasetSize; j++)
			{
				shortestPathOfTheElements = shortestPathsArray[i][j]; 
				if(shortestPathOfTheElements > 0)
				{
					numberOfPathsFound++;
					double size = shortestPathOfTheElements; 
					  Integer n = shortestPathDistibutionMap.get(size); 
					  if (n == null) { 
						  shortestPathDistibutionMap.put(size, 1); 
					  } else { 
						  shortestPathDistibutionMap.put(size, n + 1); 
					  }					
					cumulativeLengthOfThePaths+=shortestPathOfTheElements;
					if(shortestPathOfTheElements > diameter)
					{
						diameter = shortestPathOfTheElements;						
					}
				}								
			}
		System.out.println("numberOfPathsFound:"+ numberOfPathsFound+" cumulativeLengthOfThePaths:"+cumulativeLengthOfThePaths+" Avg.Path Length:"+cumulativeLengthOfThePaths/numberOfPathsFound+" diameter:"+diameter);
		
		System.out.println(shortestPathDistibutionMap);
		
		System.out.println("Sorted hashmap START");
		
		System.out.println(Utility.WorkerUtilityClass.sortHashMapByValuesD((HashMap) shortestPathDistibutionMap));
		
		System.out.println("Sorted hashmap END");		
			
		Map<Double, Integer> orderedShortestPathDistibutionMap = Utility.WorkerUtilityClass.sortHashMapByValuesD((HashMap) shortestPathDistibutionMap);
		
		
		System.out.println("BetweenNessCentrality START");
		
		WorkerUtilityClass.printArray(betweennessCentralityArray, "BetweennessCentrality");
		System.out.println("BetweenNessCentrality END");
					
		graph.getPropertiesOfTheGraphWithRespectToEdgeType().get(edgeType).shortestPathRelatedProps.setShortestPathResultParameters(shortestPathsArray, betweennessCentralityArray, cumulativeLengthOfThePaths, numberOfPathsFound, orderedShortestPathDistibutionMap, diameter);
		
		/*
		for(int i=0; i<shortestPathDistibutionMap.size();i++)
		{
			System.out.println("Retrieving all keys from the HashMap");        
			Iterator iterator = shortestPathDistibutionMap.keySet().iterator();
			while(iterator. hasNext()){        
				System.out.println(iterator.next());
			}		
		}*/
    }
	
	
	private class shortestPathDijkstraProcessorThreadClass extends Thread
    {
        int index;        
        double[][] multiResultShortestPaths;
        double[] betweennessCentralityArray;
        int numberOfAvailableCores;
        
        MapHolder myRouteMap; 
    	DijkstraEngine engine;
         
        shortestPathDijkstraProcessorThreadClass(int index, double[][] shortestPathsArray, double[] betweennessCentralityArray, int numberOfAvailableCores)
        {
             this.index=index;             
             this.numberOfAvailableCores=numberOfAvailableCores;             
             this.multiResultShortestPaths = shortestPathsArray;
             this.betweennessCentralityArray = betweennessCentralityArray;
             
             myRouteMap=new MapHolder(edgeType); 
       	  	 engine = new DijkstraEngine(myRouteMap);
        }
        
        
        public void run() 
        {
        	NodeClass x, y;
        	
        	int sizeOfTheDataSet = graph.getDataSet().size();
    		
    		ArrayList <NodeClass> dataSet = graph.getDataSet();
    		
    		int nodeOrderStartIndex = (dataSet.size()*index)/numberOfAvailableCores;
    		int nodeOrderEndIndex = (dataSet.size()*(index+1))/numberOfAvailableCores;
    		double calculatedShortestPathBetweenPairOfNodes; 
    		
    		if(nodeOrderEndIndex>dataSet.size())
    			nodeOrderEndIndex=dataSet.size();
    		
    		System.out.println("index:"+ index+ "nodeOrderStartIndex: "+nodeOrderStartIndex +"nodeOrderEndIndex: "+nodeOrderEndIndex);
    		
    		int toplamHesapSayisi=nodeOrderEndIndex-nodeOrderStartIndex;
    		int yapilanHesapSayisi;
    		
    		ArrayList <NodeClass> fullShortestPath;
        	
        	for(int i=nodeOrderStartIndex; i<nodeOrderEndIndex; i++)
        	{
        		yapilanHesapSayisi=i-nodeOrderStartIndex;
        		
        		if(i%20==0)
        			System.out.println("Kalan("+index+"):"+yapilanHesapSayisi+"/"+toplamHesapSayisi);
        		x=dataSet.get(i);
        		
        		for(int j=0; j<sizeOfTheDataSet; j++)        	
  	  			{  	  				
  	  				y = dataSet.get(j);
  	  				calculatedShortestPathBetweenPairOfNodes = calculateShortestPathBetweenNodes(x, y);
  	  				if(calculatedShortestPathBetweenPairOfNodes != DijkstraEngine.INFINITE_DISTANCE)
  	  				{  	  				
  	  					multiResultShortestPaths[x.getOrder()][y.getOrder()]=(int)calculatedShortestPathBetweenPairOfNodes;
  	  				}
  	  				
  	  				goThroughThePathAndIncreaseBetweennessCentralityTheNodesOnTheFoundPath(x, y);;
  	  			}
        	}        
        }
        
        double calculateShortestPathBetweenNodes(NodeClass x, NodeClass y)
        {
        	double currentDistance;
        	engine.execute(x, y);
    		currentDistance=engine.getShortestDistance(y);		
        	return currentDistance;    	
        }
        
        void goThroughThePathAndIncreaseBetweennessCentralityTheNodesOnTheFoundPath(NodeClass sourceNode, NodeClass destinationNode)
        {	 
        	 for (NodeClass currentNode = destinationNode; currentNode != null; currentNode = engine.getPredecessor(currentNode))
        	 {
        		 if(currentNode!=sourceNode && currentNode!=destinationNode)
        			 increaseTheNumberOfPathsPassingThroughTheNode(currentNode);
        	 } 
        }
        
        ArrayList <NodeClass> getTheFullPathForTheRecentExecution(NodeClass destinationNode)
        { 
        	 ArrayList<NodeClass> l= new ArrayList<NodeClass>();
        	 
        	 for (NodeClass currentNode = destinationNode; currentNode != null; currentNode = engine.getPredecessor(currentNode))
        	 {
        	     l.add(currentNode);        	     
        	 }
        	 
        	 Collections.reverse(l);
        	 
        	 return l;
        	 
        }
        
        synchronized boolean increaseTheNumberOfPathsPassingThroughTheNode(NodeClass currentNode)
        {
        	if(currentNode.getOrder() > betweennessCentralityArray.length)
        		return false;
        	else
        	{
        		betweennessCentralityArray[currentNode.getOrder()]++;	
        		return true;        		
        	}
        }
    }                    
    
    private shortestPathDijkstraProcessorThreadClass[] shortestPathDijkstraProcessorPool;      
}
